/**
  ******************************************************************************
  * @file    xmodem.c
  * @author  ashuai0110
  * @version V2.4
  * @date    2024-09-10
  * @brief   xmodem协议
  *
  ******************************************************************************
  * @attention
  *
  * 版权声明:内容为编者(ashuai0110)原创,使用请注明出处,当然,你也可以不这样做^_^
  * 出处链接:https://gitee.com/ashuai0110/mcu_reuse_development_module.git
  *
  * Change Logs:
  * Date          Author        Notes
  * 2024-01-07    ashuai0110    完成基本内容
  * 2024-01-14    ashuai0110    修改接收使用校验和的逻辑错误,增加一些协议信息的宏定义
  * 2024-01-21    ashuai0110    增加收发轮询处理返回状态,优化逻辑
  * 2024-01-22    ashuai0110    增加发送缓存读出完毕关闭发送中断的处理
  * 2024-03-05    ashuai0110    增加接收到脏数据时重发当前包的处理,增加部分接口
  * 2024-08-31    ashuai0110    修改超时判断逻辑,删除一些assert
  *
  ******************************************************************************
  */

/* 包含头文件-----------------------------------------------------------------*/
#include "xmodem.h"

/**
 * @addtogroup bootloader
 * @{
 */

/**
 * @defgroup xmodem xmodem
 * @{
 */

/* 私有宏定义-----------------------------------------------------------------*/
/**
 * @defgroup xmodem_local_macros xmodem local macros
 * @{
 */

/**
 * @defgroup xmodem_check_parameters_validity xmodem check parameters validity
 * @{
 */
/* xmodem接收标志有效性检查 */
#define IS_VALID_RX_FLAG(x)                                                   \
(       ((x) == XMODEM_RX_FLAG_NAK)    ||                                     \
        ((x) == XMODEM_RX_FLAG_CRC))

/**
 * @}
 */

/**
 * @}
 */

/* 私有类型定义---------------------------------------------------------------*/

/* 私有变量-------------------------------------------------------------------*/

/* 全局变量-------------------------------------------------------------------*/

/* 私有函数原型---------------------------------------------------------------*/
/**
 * @defgroup xmodem_local_functions xmodem local functions
 * @{
 */

/**
  * @brief  CRC16-XMODEM校验
  *
  * @param  data : 校验数据
  *
  * @param  len  : 校验数据长度
  *
  * @retval 返回校验结果
  */
static uint16_t xmodem_check_crc16(uint8_t *data, uint16_t len)
{
    uint8_t cnt;
    uint16_t crc = 0;

    while(len--)
    {
        crc ^= (uint16_t)(*data) << 8;
        data++;
        for(cnt = 0; cnt < 8; ++cnt)
        {
            if(crc & 0x8000)
            {
                crc = (crc << 1) ^ 0x1021;
            }
            else
            {
                crc <<= 1;
            }
        }
    }

    return crc;
}

/**
  * @brief  累加和校验
  *
  * @param  data : 校验数据
  *
  * @param  len  : 校验数据长度
  *
  * @retval 返回累加和
  */
static uint8_t xmodem_check_sum(uint8_t *data, uint16_t len)
{
    uint8_t sum = 0;
    uint16_t cnt = 0;

    for(; cnt < len; cnt++)
    {
        sum += data[cnt];
    }

    return sum;
}

/**
  * @brief  xmodem初始状态
  *
  * @param  _xmodem : xmodem管理实例
  *
  * @retval None
  */
inline static void xmodem_init_state(xmodem_t *_xmodem)
{
    _xmodem->dataLen = 0;
    _xmodem->readLen = 0;
    _xmodem->dataLenTmp = 0;
    _xmodem->rspTimeoutCnt = 0;
    _xmodem->resendTimesCnt = 0;
    _xmodem->packetNum = 1;
    _xmodem->state = XMODEM_STATE_IDLE;
    _xmodem->eotFlag = 0;
    _xmodem->recFlag = 0;
}

/**
 * @}
 */

/**
 * @defgroup xmodem_global_functions xmodem global functions
 * @{
 */

/**
  * @brief  xmodem初始化
  *
  * @param  _xmodem     : xmodem管理实例
  *
  * @param  pBuf        : 数据缓存区
  *
  * @param  bufLen      : 数据缓存区长度byte(根据xmodem.h:50行xmodem协议格式定义长度)
  *
  * @param  txEnFn      : 串口发送前准备函数
  *
  * @param  getPacketFn : 获取数据包回调函数(不用可填NULL)
  *
  * @param  setPacketFn : 设置数据包回调函数(不用可填NULL)
  *
  * @param  rspTimeout  : 回复超时时间ms
  *
  * @param  resendTimes : 超时重发次数
  *
  * @retval None
  */
void xmodem_init(xmodem_t *_xmodem, void *dataBuf, uint16_t bufLen, xmodem_tx_en_t txEnFn, \
    xmodem_user_packet_cb_t getPacketFn, xmodem_user_packet_cb_t setPacketFn, uint32_t rspTimeout, uint8_t resendTimes)
{
    MW_ASSERT_PARAM(IS_VALID_POINTER(_xmodem));
    MW_ASSERT_PARAM(IS_VALID_POINTER(dataBuf));
    MW_ASSERT_PARAM(IS_VALID_POINTER(txEnFn));
    
    /* 初始化各参数 */
    memset(_xmodem, 0, sizeof(xmodem_t));
    _xmodem->dataBuf = (uint8_t *)dataBuf;
    _xmodem->dataBufLen = bufLen;
    _xmodem->txEnFn = txEnFn;
    _xmodem->getPacketFn = getPacketFn;
    _xmodem->setPacketFn = setPacketFn;
    _xmodem->rspTimeout = rspTimeout;
    _xmodem->resendTimes = resendTimes;
    _xmodem->packetNum = 1;
}

/**
  * @brief  xmodem接收轮询处理
  *
  * @note   SecureCRT实测开启串口DMA接收1024byte数据包稳定8KB/sec
  *
  * @param  _xmodem        : xmodem管理实例
  *
  * @retval 返回接收轮询状态
  *    @arg MW_RET_OK      : 传输完成
  *    @arg MW_RET_ERR     : 传输错误
  *    @arg MW_RET_ING     : 进行中(默认处于此状态)
  *    @arg MW_RET_TIMEOUT : 回复超时
  */
uint8_t xmodem_rx_poll(xmodem_t *_xmodem)
{
    uint8_t ret = MW_RET_ING;
    uint32_t tickTmp;
    
    switch(_xmodem->state)
    {
        case XMODEM_STATE_IDLE: break;
        case XMODEM_STATE_WAIT:
            tickTmp = _xmodem->rspTimeoutCnt;
            /* 应答超时判断 */
            if(tickTmp && (mw_tick_get() - tickTmp) > _xmodem->rspTimeout)
            {
                _xmodem->rspTimeoutCnt = 0;
                /* 接受标志置位说明接收到了脏数据 */
                if(_xmodem->recFlag)
                {
                    _xmodem->dataLenTmp = _xmodem->dataLen;
                    _xmodem->state = XMODEM_STATE_CHECK_DATA; /* 切换状态 */
                    break;
                }
                /* 若回复超时次数达到预设值 则直接中断接收回到IDLE状态 */
                if(_xmodem->resendTimesCnt++ >= _xmodem->resendTimes)
                {
                    xmodem_init_state(_xmodem);
                    ret = MW_RET_TIMEOUT; /* 回复超时 */
                    break;
                }
                /* 重发当前包 */
                _xmodem->dataLen = _xmodem->dataLenTmp;
                _xmodem->txEnFn(1);
            }
            break;
        case XMODEM_STATE_CHECK_HEAD:
            /* 根据包头和数据长度进行初步检查 */
            /* SOH为包头 携带128byte数据 */
            if(XMODEM_SOH == _xmodem->dataBuf[XMODEM_START_INDEX] && (XMODEM_HEADER_SIZE + XMODEM_DATA_128_SIZE + _xmodem->checkNum) <= _xmodem->dataLen)
            {
                _xmodem->state = XMODEM_STATE_CHECK_DATA; /* 切换状态 */
                _xmodem->dataLenTmp = XMODEM_DATA_128_SIZE;
            } /* STX为包头 携带1024byte数据 */
            else if(XMODEM_STX == _xmodem->dataBuf[XMODEM_START_INDEX] && (XMODEM_HEADER_SIZE + XMODEM_DATA_1K_SIZE + _xmodem->checkNum) <= _xmodem->dataLen)
            {
                _xmodem->state = XMODEM_STATE_CHECK_DATA; /* 切换状态 */
                _xmodem->dataLenTmp = XMODEM_DATA_1K_SIZE;
            } /* EOT为包头 表示传输结束 */
            else if(XMODEM_EOT == _xmodem->dataBuf[XMODEM_START_INDEX])
            {
                xmodem_init_state(_xmodem);
                /* 回复ACK */
                _xmodem->dataBuf[XMODEM_START_INDEX] = XMODEM_ACK;
                _xmodem->dataLen = 1;
                _xmodem->txEnFn(1);
                ret = MW_RET_OK; /* 传输完成 */
                break;
            } /* CAN为包头 表示传输取消 */
            else if(XMODEM_CAN == _xmodem->dataBuf[XMODEM_START_INDEX])
            {
                xmodem_init_state(_xmodem);
                ret = MW_RET_ERR; /* 传输错误 */
                break;
            } /* 其它则回到等待状态 */
            else
            {
                _xmodem->recFlag = 1; /* 置位接收标志 */
                _xmodem->state = XMODEM_STATE_WAIT; /* 切换状态 */
                _xmodem->rspTimeoutCnt = mw_tick_get(); /* 开启超时计数 */
                break;
            }
        case XMODEM_STATE_CHECK_DATA:
            /* 包序号和包序号反码满足条件 */
            if(_xmodem->packetNum == _xmodem->dataBuf[XMODEM_NUMBER_INDEX] && 0xFF == (_xmodem->dataBuf[XMODEM_NUMBER_INDEX] + _xmodem->dataBuf[XMODEM_CNUMBER_INDEX]))
            {
                /* 1字节累加和校验 */
                if(1 == _xmodem->checkNum)
                {
                    /* 校验成功 */
                    if(_xmodem->dataBuf[_xmodem->dataLenTmp + XMODEM_HEADER_SIZE] == xmodem_check_sum(&_xmodem->dataBuf[XMODEM_DATA_INDEX], _xmodem->dataLenTmp))
                    {
                        /* 用户get回调函数 */
                        _xmodem->getPacketFn(_xmodem->packetNum++, &_xmodem->dataBuf[XMODEM_DATA_INDEX], &_xmodem->dataLenTmp);
                        _xmodem->dataBuf[XMODEM_START_INDEX] = XMODEM_ACK; /* 回复ACK */
                    } /* 校验失败 */
                    else
                    {
                        _xmodem->dataBuf[XMODEM_START_INDEX] = XMODEM_NAK; /* 回复NAK */
                    }
                } /* 2字节CRC16校验 */
                else if(2 == _xmodem->checkNum)
                {
                    /* 校验成功 */
                    if(0 == xmodem_check_crc16(&_xmodem->dataBuf[XMODEM_DATA_INDEX], _xmodem->dataLenTmp + _xmodem->checkNum))
                    {
                        _xmodem->getPacketFn(_xmodem->packetNum++, &_xmodem->dataBuf[XMODEM_DATA_INDEX], &_xmodem->dataLenTmp); /* 用户回调函数 */
                        _xmodem->dataBuf[XMODEM_START_INDEX] = XMODEM_ACK; /* 回复ACK */
                    } /* 校验失败 */
                    else
                    {
                        _xmodem->dataBuf[XMODEM_START_INDEX] = XMODEM_NAK; /* 回复NAK */
                    }
                }
            } /* 否则回复NAK */
            else
            {
                _xmodem->dataBuf[XMODEM_START_INDEX] = XMODEM_NAK;
            }
            /* 恢复默认参数 */
            _xmodem->recFlag = 0;
            _xmodem->resendTimesCnt = 0;
            _xmodem->state = XMODEM_STATE_WAIT; /* 切换状态 */
            /* 发送回复内容 */
            _xmodem->dataLen = 1;
            _xmodem->dataLenTmp = _xmodem->dataLen;
            _xmodem->txEnFn(1);
            break;
        default:
            xmodem_init_state(_xmodem);
            ret = MW_RET_ERR; /* 传输错误 */
            break;
    }
    
    return ret;
}

/**
  * @brief  xmodem接收启动
  *
  * @param  _xmodem            : xmodem管理实例
  *
  * @param  startFlag          : 接收启动标志 @ref xmodem_rx_flag
  *   @arg  XMODEM_RX_FLAG_NAK : 接收方发送XMODEM_NAK启动传输,发送方使用校验和
  *   @arg  XMODEM_RX_FLAG_CRC : 接收方发送'C'启动传输,发送方使用CRC校验
  *
  * @retval None
  */
void xmodem_rx_start(xmodem_t *_xmodem, uint8_t startFlag)
{
    MW_ASSERT_PARAM(IS_VALID_RX_FLAG(startFlag));
    
    if(XMODEM_STATE_IDLE == _xmodem->state)
    {
        _xmodem->checkNum = (startFlag == XMODEM_RX_FLAG_NAK) ? 1 : 2; /* 记录校验方式 */
        _xmodem->state = XMODEM_STATE_WAIT; /* 切换状态 */
        /* 发送启动内容 */
        _xmodem->dataBuf[XMODEM_START_INDEX] = startFlag;
        _xmodem->dataLen = 1;
        _xmodem->dataLenTmp = _xmodem->dataLen;
        _xmodem->txEnFn(1);
    }
}

/**
  * @brief  xmodem发送轮询处理
  *
  * @param  _xmodem        : xmodem管理实例
  *
  * @retval 返回发送轮询状态
  *    @arg MW_RET_OK      : 传输完成
  *    @arg MW_RET_ERR     : 传输错误
  *    @arg MW_RET_ING     : 进行中(默认处于此状态)
  *    @arg MW_RET_TIMEOUT : 回复超时
  */
uint8_t xmodem_tx_poll(xmodem_t *_xmodem)
{
    uint8_t ret = MW_RET_ING;
    uint16_t checkVal;
    uint32_t tickTmp;
    
    switch(_xmodem->state)
    {
        case XMODEM_STATE_IDLE:
            /* 逐字节查询启动命令 */
            if(_xmodem->readLen < _xmodem->dataLen)
            {
                /* 收到NAK则使用累加和校验 */
                if(XMODEM_NAK == _xmodem->dataBuf[_xmodem->readLen])
                {
                    _xmodem->checkNum = 1;
                } /* 收到'C'则使用CRC16校验 */
                else if(XMODEM_CRC16 == _xmodem->dataBuf[_xmodem->readLen])
                {
                    _xmodem->checkNum = 2;
                } /* 否则认为是无效数据 */
                else
                {
                    _xmodem->readLen++;
                    break;
                }
                _xmodem->state = XMODEM_STATE_CHECK_DATA; /* 切换状态 */
            }
            break;
        case XMODEM_STATE_WAIT:
            tickTmp = _xmodem->rspTimeoutCnt;
            /* 应答超时判断 */
            if(tickTmp && (mw_tick_get() - tickTmp) > _xmodem->rspTimeout)
            {
                _xmodem->rspTimeoutCnt = 0;
                /* 若回复超时次数达到预设值 则直接中断接收回到IDLE状态 */
                if(_xmodem->resendTimesCnt++ >= _xmodem->resendTimes)
                {
                    xmodem_init_state(_xmodem);
                    ret = MW_RET_TIMEOUT; /* 回复超时 */
                    break;
                }
                /* 重发当前包 */
                _xmodem->dataLen = _xmodem->dataLenTmp;
                _xmodem->txEnFn(1);
            }
            break;
        case XMODEM_STATE_CHECK_HEAD:
            /* 收到ACK则继续发下一包 */
            if(XMODEM_ACK == _xmodem->dataBuf[XMODEM_START_INDEX])
            {
                /* 发送EOT后收到ACK则认为是传输结束 */
                if(_xmodem->eotFlag)
                {
                    xmodem_init_state(_xmodem);
                    ret = MW_RET_OK; /* 传输完成 */
                    break;
                }
                _xmodem->state = XMODEM_STATE_CHECK_DATA; /* 切换状态 */
            } /* 收到NAK则重发当前包 */
            else if(XMODEM_NAK == _xmodem->dataBuf[XMODEM_START_INDEX])
            {
                _xmodem->packetNum--; /* 包序号-1 */
                _xmodem->state = XMODEM_STATE_CHECK_DATA; /* 切换状态 */
            } /* 收到CAN则取消传输 */
            else if(XMODEM_CAN == _xmodem->dataBuf[XMODEM_START_INDEX])
            {
                xmodem_init_state(_xmodem);
                ret = MW_RET_ERR; /* 传输错误 */
                break;
            } /* 否则认为是无效数据 */
            else
            {
                _xmodem->dataLen = 0;
                _xmodem->state = XMODEM_STATE_WAIT; /* 切换状态 */
                _xmodem->rspTimeoutCnt = mw_tick_get(); /* 开启超时计数 */
                break;
            }
        case XMODEM_STATE_CHECK_DATA:
            _xmodem->readLen = 0;
            _xmodem->resendTimesCnt = 0;
            _xmodem->state = XMODEM_STATE_WAIT; /* 切换状态 */
            /* 用户set回调函数 */
            _xmodem->setPacketFn(_xmodem->packetNum, &_xmodem->dataBuf[XMODEM_DATA_INDEX], &_xmodem->dataLen);
            /* 超过128但不超过1024的按1024byte */
            if(XMODEM_DATA_128_SIZE < _xmodem->dataLen && XMODEM_DATA_1K_SIZE >= _xmodem->dataLen)
            {
                _xmodem->dataBuf[XMODEM_START_INDEX] = XMODEM_STX; /* 包头STX携带1024byte数据 */
                memset(&_xmodem->dataBuf[XMODEM_HEADER_SIZE + _xmodem->dataLen], XMODEM_CTRLZ, XMODEM_DATA_1K_SIZE - _xmodem->dataLen); /* 填充字节 */
                _xmodem->dataLen = XMODEM_DATA_1K_SIZE;
            } /* 超过0但不超过128的按128byte */
            else if(0 < _xmodem->dataLen && XMODEM_DATA_128_SIZE >= _xmodem->dataLen)
            {
                _xmodem->dataBuf[XMODEM_START_INDEX] = XMODEM_SOH; /* 包头STX携带128byte数据 */
                memset(&_xmodem->dataBuf[XMODEM_HEADER_SIZE + _xmodem->dataLen], XMODEM_CTRLZ, XMODEM_DATA_128_SIZE - _xmodem->dataLen); /* 填充字节 */
                _xmodem->dataLen = XMODEM_DATA_128_SIZE;
            } /* 其它认为是传输结束 */
            else
            {
                _xmodem->eotFlag = 1;
                /* 回复EOT传输结束 */
                _xmodem->dataBuf[XMODEM_START_INDEX] = XMODEM_EOT;
                _xmodem->dataLen = 1;
                _xmodem->dataLenTmp = _xmodem->dataLen;
                _xmodem->txEnFn(1);
                break;
            }
            /* 1字节累加和校验 */
            if(1 == _xmodem->checkNum)
            {
                checkVal = xmodem_check_sum(&_xmodem->dataBuf[XMODEM_DATA_INDEX], _xmodem->dataLen);
                _xmodem->dataBuf[_xmodem->dataLen + XMODEM_HEADER_SIZE] = checkVal;
            } /* 2字节CRC16校验 */
            else if(2 == _xmodem->checkNum)
            {
                checkVal = xmodem_check_crc16(&_xmodem->dataBuf[XMODEM_DATA_INDEX], _xmodem->dataLen);
                _xmodem->dataBuf[_xmodem->dataLen + XMODEM_HEADER_SIZE] = checkVal >> 8;
                _xmodem->dataBuf[_xmodem->dataLen + XMODEM_HEADER_SIZE + 1] = checkVal & 0xFF;
            }
            /* 设置包序号和其反码 */
            _xmodem->dataBuf[XMODEM_NUMBER_INDEX] = _xmodem->packetNum;
            _xmodem->dataBuf[XMODEM_CNUMBER_INDEX] = 0xFF - _xmodem->packetNum;
            _xmodem->packetNum++;
            /* 发送包内容 */
            _xmodem->dataLen = _xmodem->dataLen + XMODEM_HEADER_SIZE + _xmodem->checkNum;
            _xmodem->dataLenTmp = _xmodem->dataLen;
            _xmodem->txEnFn(1);
            break;
        default:
            xmodem_init_state(_xmodem);
            ret = MW_RET_ERR; /* 传输错误 */
            break;
    }
    
    return ret;
}

/**
  * @brief  写入数据到接收缓冲区
  *
  * @note   一般加入到(串口)接收中断
  *
  * @param  _xmodem : xmodem管理实例
  *
  * @param  pBuf    : 数据缓存区
  *
  * @param  len     : 写入长度byte
  *
  * @retval 返回实际写入的数据数量
  */
uint16_t xmodem_write_rx_buf(xmodem_t *_xmodem, void *pBuf, uint16_t len)
{
    uint16_t unusedLen;
    
    /* 在IDLE和WAIT状态下可以接收数据 */
    if(XMODEM_STATE_WAIT >= _xmodem->state)
    {
        /* 计算可写数量 */
        unusedLen = _xmodem->dataBufLen - _xmodem->dataLen;
        len = (len > unusedLen) ? unusedLen : len;
        
        if(0 < len)
        {
            /* 接续copy数据并记录数量 */
            memcpy((void *)&_xmodem->dataBuf[_xmodem->dataLen], (void *)pBuf, len);
            _xmodem->dataLen += len;
            _xmodem->rspTimeoutCnt = 0; /* 关闭回复超时计数 */
            if(XMODEM_STATE_WAIT == _xmodem->state)
            {
                _xmodem->state = XMODEM_STATE_CHECK_HEAD; /* 切换状态 */
            }

            return len;
        }
    }
    
    return 0;
}

/**
  * @brief  读出数据从发送缓冲区
  *
  * @note   一般加入到(串口)发送中断
  *
  * @param  _xmodem : xmodem管理实例
  *
  * @param  pBuf    : 数据存储区
  *
  * @param  len     : 读出长度byte
  *
  * @retval 返回实际读出的数据数量
  */
uint16_t xmodem_read_tx_buf(xmodem_t *_xmodem, void *pBuf, uint16_t len)
{
    uint16_t unreadLen;
    
    /* 计算可读数量 */
    unreadLen = _xmodem->dataLen - _xmodem->readLen;
    len = (len > unreadLen) ? unreadLen : len;
    
    if(0 < len)
    {
        /* copy数据至外部存储区并记录数量 */
        memcpy((void *)pBuf, (void *)&_xmodem->dataBuf[_xmodem->readLen], len);
        _xmodem->readLen += len;
        /* 数据被全部copy出去 */
        if(_xmodem->readLen == _xmodem->dataLen)
        {
            /* 清零相关计数 */
            _xmodem->dataLen = 0;
            _xmodem->readLen = 0;
            _xmodem->rspTimeoutCnt = mw_tick_get(); /*开启回复超时计数*/
        }
    }
    else
    {
        _xmodem->txEnFn(0); /* 关闭发送中断 */
    }
    
    return len;
}

/**
  * @brief  xmodem设置回复超时时间ms
  *
  * @param  _xmodem    : xmodem管理实例
  *
  * @param  rspTimeout : 回复超时时间ms
  *
  * @retval None
  */
void xmodem_set_rsp_timeout(xmodem_t *_xmodem, uint32_t rspTimeout)
{
    _xmodem->rspTimeout = rspTimeout; 
}

/**
  * @brief  xmodem获取回复超时时间ms
  *
  * @param  _xmodem : xmodem管理实例
  *
  * @retval 返回回复超时时间ms
  */
uint32_t xmodem_get_rsp_timeout(xmodem_t *_xmodem)
{
    return _xmodem->rspTimeout; 
}

/**
  * @brief  xmodem设置重发次数
  *
  * @param  _xmodem     : xmodem管理实例
  *
  * @param  resendTimes : 重发次数
  *
  * @retval None
  */
void xmodem_set_resend_times(xmodem_t *_xmodem, uint8_t resendTimes)
{
    _xmodem->resendTimes = resendTimes; 
}

/**
  * @brief  xmodem获取重发次数
  *
  * @param  _xmodem : xmodem管理实例
  *
  * @retval 返回重发次数
  */
uint8_t xmodem_get_resend_times(xmodem_t *_xmodem)
{
    return _xmodem->resendTimes; 
}

/**
 * @}
 */

/**
 * @}
 */

/**
 * @}
 */
